package com.gitee.wsl.func.filter.bloomfilter

import com.gitee.wsl.func.filter.bloomfilter.DefaultBloomFilterTest.SparseDefaultBloomFilter
import com.gitee.wsl.func.filter.bloomfilter.api.BitMapExtractor
import com.gitee.wsl.func.filter.bloomfilter.api.BloomFilter
import com.gitee.wsl.func.filter.bloomfilter.api.BloomFilter.Companion.SPARSE
import com.gitee.wsl.func.filter.bloomfilter.api.IndexExtractor
import com.gitee.wsl.func.filter.bloomfilter.bean.BitMaps.getLongBit
import com.gitee.wsl.func.filter.bloomfilter.bean.Shape
import com.gitee.wsl.func.predicate.IntPredicate
import com.gitee.wsl.func.predicate.LongPredicate
import com.gitee.wsl.func.predicate.Predicate
import com.gitee.wsl.text.format.format
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue


/**
 * Tests for the [BloomFilter].
 */
class DefaultBloomFilterTest : AbstractBloomFilterTest<SparseDefaultBloomFilter>() {
    abstract class AbstractDefaultBloomFilter<T : AbstractDefaultBloomFilter<T>>(
        override val shape: Shape
    ) : BloomFilter<T> {
        
        protected var indices: MutableSet<Int> = mutableSetOf()

//        init {
//            this.indices = TreeSet()
//        }

        public override fun cardinality(): Int {
            return indices.size
        }

        private fun checkIndicesRange() {
            if (!indices.isEmpty()) {
                require(indices.last() < shape.numberOfBits) {
                    String.format(
                        "Value in list %s is greater than maximum value (%s)", indices.last(),
                        shape.numberOfBits
                    )
                }
                require(indices.first() >= 0) {
                    String.format(
                        "Value in list %s is less than 0",
                        indices.first()
                    )
                }
            }
        }

        public override fun clear() {
            indices.clear()
        }

        public override fun contains(bitMapExtractor: BitMapExtractor): Boolean {
            return contains(IndexExtractor.fromBitMapExtractor(bitMapExtractor))
        }

        public override fun contains(indexExtractor: IndexExtractor): Boolean {
            return indexExtractor.processIndices(indices::contains)
        }


        public override fun merge(bitMapExtractor: BitMapExtractor): Boolean {
            return merge(IndexExtractor.fromBitMapExtractor(bitMapExtractor!!))
        }

        public override fun merge(indexExtractor: IndexExtractor): Boolean {
            val result = indexExtractor.processIndices(Predicate { x: Int ->
                indices.add(x)
                true
            })
            checkIndicesRange()
            return result
        }

        public override fun processBitMaps(consumer: LongPredicate): Boolean {
            return BitMapExtractor.fromIndexExtractor(this, shape.numberOfBits).processBitMaps(consumer)
        }

        public override fun processIndices(consumer: IntPredicate): Boolean {
            for (i in indices) {
                if (!consumer.test(i)) {
                    return false
                }
            }
            return true
        }
    }

    internal class BrokenCardinality(shape: Shape) : NonSparseDefaultBloomFilter(shape) {
        override fun cardinality(): Int {
            return super.cardinality() + 1
        }
    }

    /**
     * A default implementation of a non-sparse Bloom filter.
     */
    open class NonSparseDefaultBloomFilter(shape: Shape) : AbstractDefaultBloomFilter<NonSparseDefaultBloomFilter>(shape) {
        public override fun characteristics(): Int {
            return 0
        }

        override fun copy(): NonSparseDefaultBloomFilter {
           val result = NonSparseDefaultBloomFilter(shape)
            result.indices.addAll(indices)
            return result
        }

    }

    /**
     * A default implementation of a Sparse bloom filter.
     */
    class SparseDefaultBloomFilter(shape: Shape) : AbstractDefaultBloomFilter<SparseDefaultBloomFilter>(shape) {
        public override fun characteristics(): Int {
            return SPARSE
        }

        public override fun copy(): SparseDefaultBloomFilter {
            val result = SparseDefaultBloomFilter(shape)
            result.indices.addAll(indices)
            return result
        }
    }

    protected override fun createEmptyFilter(shape: Shape): SparseDefaultBloomFilter {
        return SparseDefaultBloomFilter(shape)
    }

    @Test
    fun testDefaultBloomFilterSimpleSpecificMerge() {
        val filter = SparseDefaultBloomFilter(Shape.fromKM(3, 150))
        val hasher = IncrementingHasher(0, 1)
        assertTrue(filter.merge(hasher))
        assertEquals(3, filter.cardinality())
    }

    @Test
    fun testDefaultBloomFilterSparseSpecificMerge() {
        val shape: Shape = Shape.fromKM(3, 150)
        val filter: AbstractDefaultBloomFilter<*> = SparseDefaultBloomFilter(shape)
        val filter2 = createFilter(shape, IncrementingHasher(0, 1))
        val newFilter = filter.copy()
        newFilter.merge(filter2)
        assertEquals(3, newFilter.cardinality())
    }

    @Test
    fun testEstimateLargeN() {
        val s: Shape = Shape.fromKM(1, Int.MAX_VALUE)
        // create a very large filter with Integer.MAX_VALUE-1 bits set.
        val bf1 = SimpleBloomFilter(s)
        bf1.merge(BitMapExtractor { predicate: Predicate<Long> ->
            var limit = Int.MAX_VALUE - 1
            val max = (Int.MAX_VALUE - 1).toLong()
            while (limit > 64) {
                predicate.test(max)
                limit -= 64
            }
            var last = 0L
            for (i in 0..<limit) {
                last = last or getLongBit(i)
            }
            predicate.test(last)
            true
        })
        // the actual result of the calculation is: 46144189292, so the returned value
        // should be Integer.MAX_VALUE.
        assertEquals(Int.MAX_VALUE, bf1.estimateN())
    }

//    @Test
//    fun testEstimateNWithBrokenCardinality() {
//        // build a filter
//        val filter1 = TestingHashers.populateEntireFilter(BrokenCardinality(testShape))
//        assertThrows(java.lang.IllegalArgumentException::class.java, { filter1.estimateN() })
//    }

    @Test
    fun testHasherBasedMergeWithDifferingSparseness() {
        val hasher = IncrementingHasher(1, 1)

        var bf1:AbstractDefaultBloomFilter<*> = NonSparseDefaultBloomFilter(testShape)
        bf1.merge(hasher)
        assertTrue(
            BitMapExtractor.fromIndexExtractor(hasher.indices(testShape), testShape.numberOfBits)
                .processBitMapPairs(bf1, { x, y -> x == y })
        )

        bf1 = SparseDefaultBloomFilter(testShape)
        bf1.merge(hasher)
        assertTrue(
            BitMapExtractor.fromIndexExtractor(hasher.indices(testShape), testShape.numberOfBits)
                .processBitMapPairs(bf1, { x, y -> x == y })
        )
    }

    @Test
    fun testIntersectionLimit() {
        val s: Shape = Shape.fromKM(1, Int.MAX_VALUE)
        // create a very large filter with Integer.MAX_VALUE-1 bit set.
        val bf1 = SimpleBloomFilter(s)
        bf1.merge(BitMapExtractor { predicate: Predicate<Long> ->
            var limit = Int.MAX_VALUE - 1
            while (limit > 64) {
                predicate.test(-0x1L)
                limit -= 64
            }
            var last = 0L
            for (i in 0..<limit) {
                last = last or getLongBit(i)
            }
            predicate.test(last)
            true
        })
        // the actual result of the calculation is: 46144189292
        assertEquals(Int.MAX_VALUE, bf1.estimateIntersection(bf1))
    }

    @Test
    fun testSparseNonSparseMerging() {
        val bf1 = SparseDefaultBloomFilter(testShape)
        bf1.merge(TestingHashers.FROM1)
        val bf2= NonSparseDefaultBloomFilter(testShape)
        bf2.merge(TestingHashers.FROM11)

        var result :AbstractDefaultBloomFilter<*> = bf1.copy()
        result.merge(bf2)
        assertEquals(27, result.cardinality())

        result = bf2.copy()
        result.merge(bf1)
        assertEquals(27, result.cardinality())
    }
}
